åå®å šãªèªå¯ã«é¢ããå æ¬çã¬ã€ãã§ãå ç¢ãªã¢ããªã±ãŒã·ã§ã³ã»ãã¥ãªãã£ãå®çŸããŸãããããã°ãé²ããéçºè äœéšãåäžãããã¹ã±ãŒã©ãã«ãªã¢ã¯ã»ã¹å¶åŸ¡ãæ§ç¯ããããã®åå®å šãªæš©éã·ã¹ãã ã®å°å ¥æ¹æ³ã解説ããŸãã
ã³ãŒãã®å ç¢åïŒåå®å šãªèªå¯ãšæš©é管çã®è©³çŽ°è§£èª¬
ãœãããŠã§ã¢éçºã®è€éãªäžçã«ãããŠãã»ãã¥ãªãã£ã¯æ©èœã§ã¯ãªããåºæ¬çãªèŠä»¶ã§ããç§ãã¡ã¯ãã¡ã€ã¢ãŠã©ãŒã«ãæ§ç¯ããããŒã¿ãæå·åããã€ã³ãžã§ã¯ã·ã§ã³ããä¿è·ããŸããããããäžè¬çã§é°æ¹¿ãªè匱æ§ããã¢ããªã±ãŒã·ã§ã³ããžãã¯ã®å¥¥æ·±ããç®ã«èŠããå Žæã«æœãã§ããããšããããããŸãããããèªå¯ãå ·äœçã«ã¯æš©éã®ç®¡çæ¹æ³ã§ããé·å¹Žãéçºè ã¯ãæååããŒã¹ã®æš©éããšãããäžèŠç¡å®³ã«èŠãããã¿ãŒã³ã«äŸåããŠããŸããããã®æ¹æ³ã¯ãå§ãã¯ã·ã³ãã«ã§ããããã°ãã°è匱ã§ããšã©ãŒãçºçãããããå®å šã§ãªãã·ã¹ãã ã«ã€ãªãããŸãããããéçºããŒã«ã掻çšããŠãèªå¯ãšã©ãŒãæ¬çªç°å¢ã«å°éããåã«ãã£ããã§ãããã©ãã§ããããïŒãããã³ã³ãã€ã©èªäœãç§ãã¡ã®ç¬¬äžã®é²åŸ¡ç·ãšãªãåŸããšãããïŒãããããåå®å šãªèªå¯ã®äžçãžã
ãã®ã¬ã€ãã§ã¯ãæååããŒã¹ã®æš©éãšããè匱ãªäžçãããå ç¢ã§ä¿å®æ§ãé«ããéåžžã«å®å šãªåå®å šèªå¯ã·ã¹ãã ãæ§ç¯ãããŸã§ãå æ¬çãªæ ã«ãæ¡å ããŸããéçåä»ãèšèªå šè¬ã«é©çšå¯èœãªæŠå¿µã説æããããã«ãTypeScriptã§ã®å®è·µçãªäŸãçšããŠãããªãããäœãããã©ã®ããã«ããæ¢æ±ããŸããæåŸãŸã§èªãã°ãçè«ãçè§£ããã ãã§ãªããã¢ããªã±ãŒã·ã§ã³ã®ã»ãã¥ãªãã£äœå¶ã匷åããéçºè äœéšãé£èºçã«åäžãããæš©é管çã·ã¹ãã ãå®è£ ããããã®å®è·µçãªç¥èã身ã«ã€ããããšãã§ããã§ãããã
æååããŒã¹ã®æš©éã®è匱æ§ïŒããããèœãšã穎
èªå¯ã®æ žå¿ã¯ãããã®ãŠãŒã¶ãŒã¯ãã®ã¢ã¯ã·ã§ã³ãå®è¡ããæš©éãæã£ãŠãããïŒããšããåçŽãªåãã«çããããšã§ããæš©éã衚ãæãçŽæ¥çãªæ¹æ³ã¯ã"edit_post"ã"delete_user"ã®ãããªæååã§ããããã«ãããæ¬¡ã®ãããªã³ãŒããçãŸããŸãã
if (user.hasPermission("create_product")) { ... }
ãã®ã¢ãããŒãã¯æåã¯å®è£ ãç°¡åã§ãããç äžã®æ¥Œé£ã§ãããããžãã¯ã¹ããªã³ã°ãã®äœ¿çšãšããŠç¥ããããã®æ £è¡ã¯ãå€å€§ãªãªã¹ã¯ãšæè¡çè² åµããããããŸãããªããã®ãã¿ãŒã³ãããã»ã©åé¡ãªã®ããåæããŠã¿ãŸãããã
ãšã©ãŒã®é£é
- ãµã€ã¬ã³ããªã¿ã€ãïŒ ãããæãæçœãªåé¡ã§ãã
"create_product"ã®ä»£ããã«"create_pruduct"ããã§ãã¯ãããããªåçŽãªã¿ã€ããã¹ã¯ãã¯ã©ãã·ã¥ãåŒãèµ·ãããŸãããèŠåãã衚瀺ãããŸããããã§ãã¯ã¯ãã éãã«å€±æããã¢ã¯ã»ã¹æš©ãæã€ã¹ããŠãŒã¶ãŒãæåŠãããŸããããã«æªãããšã«ãæš©éå®çŸ©ã®ã¿ã€ããæå³ããã¢ã¯ã»ã¹ãèš±å¯ããŠããŸãå¯èœæ§ããããŸãããããã®ãã°ã¯è¿œè·¡ãéåžžã«å°é£ã§ãã - çºèŠå¯èœæ§ã®æ¬ åŠïŒ æ°ããéçºè ãããŒã ã«åå ãããšããã©ã®æš©éãå©çšå¯èœããç¥ãã«ã¯ã©ãããã°ããã§ããããïŒåœŒãã¯ã³ãŒãããŒã¹å šäœãæ€çŽ¢ãããã¹ãŠã®äœ¿çšç®æãèŠã€ãåºããããããŸãããä¿¡é Œã§ããå¯äžã®æ å ±æºïŒSingle Source of TruthïŒã¯ãªãããªãŒãã³ã³ããªãŒãããªããã³ãŒãèªäœãæäŸããããã¥ã¡ã³ãããããŸããã
- ãªãã¡ã¯ã¿ãªã³ã°ã®æªå€¢ïŒ ããªãã®çµç¹ãããæ§é åãããåœåèŠåãæ¡çšãã
"edit_post"ã"post:update"ã«å€æŽãããšæ±ºãããšæ³åããŠã¿ãŠãã ãããããã«ã¯ãããã¯ãšã³ããããã³ããšã³ãããããŠå Žåã«ãã£ãŠã¯ããŒã¿ããŒã¹ã®ãšã³ããªã«è³ããŸã§ãã³ãŒãããŒã¹å šäœã§å€§æåãšå°æåãåºå¥ããã°ããŒãã«ãªæ€çŽ¢ãšçœ®ææäœãå¿ èŠã§ããããã¯ãªã¹ã¯ã®é«ãæåããã»ã¹ã§ãããäžã€ã§ãèŠéããšæ©èœãå£ããããã»ãã¥ãªãã£ããŒã«ãçãŸãããããå¯èœæ§ããããŸãã - ã³ã³ãã€ã«æã®å®å
šæ§ããªãïŒ æ ¹æ¬çãªåŒ±ç¹ã¯ãæš©éæååã®æå¹æ§ãå®è¡æã«ãããã§ãã¯ãããªãããšã§ããã³ã³ãã€ã©ã¯ã©ã®æååãæå¹ãªæš©éã§ãã©ããç¡å¹ããç¥ããŸãããã³ã³ãã€ã©ã«ãšã£ãŠ
"delete_user"ãš"delete_useeer"ã¯çããæå¹ãªæååã§ããããšã©ãŒã®çºèŠã¯ãŠãŒã¶ãŒããã¹ããã§ãŒãºã«å§ããããŸãã
倱æã®å ·äœäŸ
ããã¥ã¡ã³ãã¢ã¯ã»ã¹ãå¶åŸ¡ããããã¯ãšã³ããµãŒãã¹ãèããŠã¿ãŸããããããã¥ã¡ã³ããåé€ããæš©éã¯"document_delete"ãšããŠå®çŸ©ãããŠããŸãã
管çããã«ã«åãçµãã§ããéçºè ã¯ãåé€ãã¿ã³ã远å ããå¿ èŠããããŸããåœŒã¯æ¬¡ã®ããã«ãã§ãã¯ãèšè¿°ããŸãã
// APIãšã³ããã€ã³ãå
ã«ãŠ
if (currentUser.hasPermission("document:delete")) {
// åé€åŠçãç¶è¡
} else {
return res.status(403).send("Forbidden");
}
éçºè
ã¯ãæ°ããèŠçŽã«åŸããã¢ã³ããŒã¹ã³ã¢ïŒ_ïŒã®ä»£ããã«ã³ãã³ïŒ:ïŒã䜿çšããŸãããã³ãŒãã¯æ§æçã«æ£ããããã¹ãŠã®ãªã³ãã£ã³ã°ã«ãŒã«ãééããŸãããããããããã€ããããšãã©ã®ç®¡çè
ãããã¥ã¡ã³ããåé€ã§ããªããªããŸããæ©èœã¯å£ããŠããŸãããã·ã¹ãã ã¯ã¯ã©ãã·ã¥ããŸããããã 403 Forbiddenãšã©ãŒãè¿ãã ãã§ãããã®ãã°ã¯æ°æ¥ããæ°é±éæ°ã¥ãããªãå¯èœæ§ãããããŠãŒã¶ãŒã®äžæºãåŒãèµ·ããããã£ãäžæåã®ééããçºèŠããããã«éªšã®æãããããã°ã»ãã·ã§ã³ãå¿
èŠã«ãªããŸãã
ããã¯ããããã§ãã·ã§ãã«ãªãœãããŠã§ã¢ãæ§ç¯ããããã®æç¶å¯èœã§å®å šãªæ¹æ³ã§ã¯ãããŸãããç§ãã¡ã¯ããè¯ãã¢ãããŒããå¿ èŠãšããŠããŸãã
åå®å šãªèªå¯ã®å°å ¥ïŒã³ã³ãã€ã©ã第äžã®é²åŸ¡ç·ã«
åå®å šãªèªå¯ã¯ãã©ãã€ã ã·ããã§ããã³ã³ãã€ã©ãäœãç¥ããªãä»»æã®æååãšããŠæš©éã衚ã代ããã«ãããã°ã©ãã³ã°èšèªã®åã·ã¹ãã å ã§æç€ºçãªåãšããŠå®çŸ©ããŸãããã®åçŽãªå€æŽã«ãããæš©éã®æ€èšŒã¯å®è¡æã®æžå¿µããã³ã³ãã€ã«æã®ä¿èšŒãžãšç§»è¡ããŸãã
åå®å
šãªã·ã¹ãã ã䜿çšãããšãã³ã³ãã€ã©ã¯æå¹ãªæš©éã®å®å
šãªã»ãããçè§£ããŸããååšããªãæš©éããã§ãã¯ããããšãããšãã³ãŒãã¯ã³ã³ãã€ã«ãããããŸãããå
ã®äŸã®ã¿ã€ãã"document:delete" vs "document_delete"ã¯ããã¡ã€ã«ãä¿åããåã«ã³ãŒããšãã£ã¿ã§å³åº§ã«çºèŠãããèµ€ç·ã§ç€ºãããŸãã
åºæ¬åå
- äžå çãªå®çŸ©ïŒ ãã¹ãŠã®å¯èœãªæš©éã¯ãåäžã®å ±æãããå Žæã§å®çŸ©ãããŸãããã®ãã¡ã€ã«ãã¢ãžã¥ãŒã«ã¯ãã¢ããªã±ãŒã·ã§ã³å šäœã®ã»ãã¥ãªãã£ã¢ãã«ã«ãšã£ãŠãæºãããªãä¿¡é Œã§ããå¯äžã®æ å ±æºãšãªããŸãã
- ã³ã³ãã€ã«æã®æ€èšŒïŒ åã·ã¹ãã ã¯ããã§ãã¯ãããŒã«å®çŸ©ãUIã³ã³ããŒãã³ããªã©ãæš©éãžã®ãããªãåç §ããæå¹ã§æ¢åã®æš©éã§ããããšãä¿èšŒããŸããã¿ã€ããååšããªãæš©éã¯äžå¯èœã«ãªããŸãã
- éçºè
äœéšïŒDXïŒã®åäžïŒ éçºè
ã¯
user.hasPermission(...)ãšå ¥åããéã«ãIDEã®ãªãŒãã³ã³ããªãŒãã®ãããªæ©èœãå©çšã§ããŸããå©çšå¯èœãªãã¹ãŠã®æš©éã®ããããããŠã³ã衚瀺ãããã·ã¹ãã ãèªå·±ææžåããããšãšãã«ãæ£ç¢ºãªæååå€ãèšæ¶ãã粟ç¥çè² æ ã軜æžããŸãã - èªä¿¡ãæã£ããªãã¡ã¯ã¿ãªã³ã°ïŒ æš©éã®ååã倿Žããå¿ èŠãããå ŽåãIDEã«çµã¿èŸŒãŸããŠãããªãã¡ã¯ã¿ãªã³ã°ããŒã«ã䜿çšã§ããŸããæš©éããã®å®çŸ©å ã§åå倿Žãããšããããžã§ã¯ãå šäœã®ãã¹ãŠã®äœ¿çšç®æãèªåçãã€å®å šã«æŽæ°ãããŸãããã€ãŠã¯ãªã¹ã¯ã®é«ãæåã¿ã¹ã¯ã ã£ããã®ããäºçްã§å®å šãªèªååãããã¿ã¹ã¯ã«å€ãããŸãã
åºç€ã®æ§ç¯ïŒåå®å šãªæš©éã·ã¹ãã ã®å®è£
çè«ããå®è·µã«ç§»ããŸãããããŒãããå®å šãªåå®å šæš©éã·ã¹ãã ãæ§ç¯ããŸããäŸãšããŠTypeScriptã䜿çšããŸããããã®åŒ·åãªåã·ã¹ãã ã¯ãã®ã¿ã¹ã¯ã«æé©ã§ããããããæ ¹æ¬çãªååã¯ãC#ãJavaãSwiftãKotlinãRustã®ãããªä»ã®éçåä»ãèšèªã«ã容æã«é©å¿ã§ããŸãã
ã¹ããã1ïŒæš©éã®å®çŸ©
æåã«ããŠæãéèŠãªã¹ãããã¯ããã¹ãŠã®æš©éã«å¯Ÿããä¿¡é Œã§ããå¯äžã®æ å ±æºãäœæããããšã§ãããããéæããã«ã¯ããã€ãã®æ¹æ³ããããããããã«ãã¬ãŒããªãããããŸãã
ãªãã·ã§ã³AïŒæååãªãã©ã«ãŠããªã³åã䜿çšãã
ãããæãã·ã³ãã«ãªã¢ãããŒãã§ãããã¹ãŠã®å¯èœãªæš©éæååã®ãŠããªã³ã§ããåãå®çŸ©ããŸããå°èŠæš¡ãªã¢ããªã±ãŒã·ã§ã³ã«ã¯ç°¡æœã§å¹æçã§ãã
// src/permissions.ts
export type Permission =
| "user:create"
| "user:read"
| "user:update"
| "user:delete"
| "post:create"
| "post:read"
| "post:update"
| "post:delete";
é·æïŒ èšè¿°ãšçè§£ãéåžžã«ã·ã³ãã«ã§ãã
çæïŒ æš©éã®æ°ãå¢ããã«ã€ããŠæ±ãã«ãããªãå¯èœæ§ããããŸããé¢é£ããæš©éãã°ã«ãŒãåããæ¹æ³ããªãã䜿çšããéã«ã¯äŸç¶ãšããŠæååãæã¡èŸŒãå¿
èŠããããŸãã
ãªãã·ã§ã³BïŒEnumã䜿çšãã
Enumã¯ãé¢é£ãã宿°ãåäžã®ååã®äžã«ã°ã«ãŒãåããæ¹æ³ãæäŸããã³ãŒããããèªã¿ãããããããšãã§ããŸãã
// src/permissions.ts
export enum Permission {
UserCreate = "user:create",
UserRead = "user:read",
UserUpdate = "user:update",
UserDelete = "user:delete",
PostCreate = "post:create",
// ... ãªã©
}
é·æïŒ ååä»ã宿°ïŒPermission.UserCreateïŒãæäŸããæš©éã䜿çšããéã®ã¿ã€ããé²ãããšãã§ããŸãã
çæïŒ TypeScriptã®Enumã«ã¯ããã€ãã®çããããä»ã®ã¢ãããŒãã»ã©æè»ã§ãªãå ŽåããããŸãããŠããªã³åã®ããã«æååå€ãæœåºããã«ã¯è¿œå ã®ã¹ããããå¿
èŠã§ãã
ãªãã·ã§ã³CïŒ`as const`ãçšãããªããžã§ã¯ãã¢ãããŒãïŒæšå¥šïŒ
ãããæã匷åã§ã¹ã±ãŒã©ãã«ãªã¢ãããŒãã§ããTypeScriptã®`as const`ã¢ãµãŒã·ã§ã³ã䜿çšããŠãæ·±ããã¹ããããèªã¿åãå°çšãªããžã§ã¯ãã§æš©éãå®çŸ©ããŸããããã«ãããæŽçããããèšæ³ã«ããçºèŠå¯èœæ§ïŒäŸïŒPermissions.USER.CREATEïŒãããã³ãã¹ãŠã®æš©éæååã®ãŠããªã³åãåçã«çæããèœåãšããããã¹ãŠã®å©ç¹ã享åã§ããŸãã
以äžã«ãã®èšå®æ¹æ³ã瀺ããŸãã
// src/permissions.ts
// 1. 'as const' ã䜿ã£ãŠæš©éãªããžã§ã¯ããå®çŸ©
export const Permissions = {
USER: {
CREATE: "user:create",
READ: "user:read",
UPDATE: "user:update",
DELETE: "user:delete",
},
POST: {
CREATE: "post:create",
READ: "post:read",
UPDATE: "post:update",
DELETE: "post:delete",
},
BILLING: {
READ_INVOICES: "billing:read_invoices",
MANAGE_SUBSCRIPTION: "billing:manage_subscription",
}
} as const;
// 2. ãã¹ãŠã®æš©éå€ãæœåºããããã®ãã«ããŒåãäœæ
type TPermissions = typeof Permissions;
// ãã®ãŠãŒãã£ãªãã£åã¯ããã¹ãããããªããžã§ã¯ãã®å€ãååž°çã«ãã©ããåããŠãŠããªã³ã«ããŸã
type FlattenObjectValues
ãã®ã¢ãããŒããåªããŠããã®ã¯ãæš©éã«æç¢ºã§éå±€çãªæ§é ãæäŸããããã§ãããããã¯ã¢ããªã±ãŒã·ã§ã³ãæé·ããã«ã€ããŠéåžžã«éèŠã«ãªããŸããé²èЧã容æã§ã`AllPermissions`åãèªåçã«çæãããããããŠããªã³åãæåã§æŽæ°ããå¿ èŠããããŸãããããããç§ãã¡ã®ã·ã¹ãã ã®æ®ãã®éšåã§äœ¿çšããåºç€ãšãªããŸãã
ã¹ããã2ïŒããŒã«ã®å®çŸ©
ããŒã«ãšã¯ãåã«æš©éã®ååä»ãã³ã¬ã¯ã·ã§ã³ã§ãã`AllPermissions`åã䜿çšããŠãããŒã«å®çŸ©ãåå®å šã§ããããšãä¿èšŒã§ããããã«ãªããŸããã
// src/roles.ts
import { Permissions, AllPermissions } from './permissions';
// ããŒã«ã®æ§é ãå®çŸ©
export type Role = {
name: string;
description: string;
permissions: AllPermissions[];
};
// ã¢ããªã±ãŒã·ã§ã³ã®ãã¹ãŠã®ããŒã«ã®ã¬ã³ãŒããå®çŸ©
export const AppRoles: Record
æš©éãå²ãåœãŠãéã«`Permissions`ãªããžã§ã¯ãïŒäŸïŒ`Permissions.POST.READ`ïŒã䜿çšããŠããããšã«æ³šç®ããŠãã ãããããã«ããã¿ã€ããé²ããæå¹ãªæš©éã®ã¿ãå²ãåœãŠãŠããããšãä¿èšŒãããŸãã`ADMIN`ããŒã«ã«ã€ããŠã¯ã`Permissions`ãªããžã§ã¯ããããã°ã©ã ã§ãã©ããåããŠãã¹ãŠã®æš©éãä»äžããæ°ããæš©éã远å ãããéã«ç®¡çè ãèªåçã«ãããç¶æ¿ããããã«ããŠããŸãã
ã¹ããã3ïŒåå®å šãªãã§ãã«ãŒé¢æ°ã®äœæ
ãããç§ãã¡ã®ã·ã¹ãã ã®èŠã§ãããŠãŒã¶ãŒãç¹å®ã®æš©éãæã£ãŠãããã©ããããã§ãã¯ã§ãã颿°ãå¿ èŠã§ããéµãšãªãã®ã¯é¢æ°ã®ã·ã°ããã£ã§ãããã«ããæå¹ãªæš©éã®ã¿ããã§ãã¯ããããã匷å¶ãããŸãã
ãŸãã`User`ãªããžã§ã¯ããã©ã®ããã«ãªãããå®çŸ©ããŸãããã
// src/user.ts
import { AppRoleKey } from './roles';
export type User = {
id: string;
email: string;
roles: AppRoleKey[]; // ãŠãŒã¶ãŒã®ããŒã«ãåå®å
šã§ãïŒ
};
次ã«ãèªå¯ããžãã¯ãæ§ç¯ããŸããããå¹çãèãããšããŠãŒã¶ãŒã®å šæš©éã»ãããäžåºŠèšç®ãããã®ã»ããã«å¯ŸããŠãã§ãã¯ããã®ãæåã§ãã
// src/authorization.ts
import { User } from './user';
import { AppRoles } from './roles';
import { AllPermissions } from './permissions';
/**
* æå®ããããŠãŒã¶ãŒã®å®å
šãªæš©éã»ãããèšç®ããŸãã
* å¹ççãªO(1)ã«ãã¯ã¢ããã®ããã«Setã䜿çšããŸãã
* @param user ãŠãŒã¶ãŒãªããžã§ã¯ãã
* @returns ãŠãŒã¶ãŒãæã€ãã¹ãŠã®æš©éãå«ãSetã
*/
function getUserPermissions(user: User): Set
`hasPermission`颿°ã®`permission: AllPermissions`ãã©ã¡ãŒã¿ã«éæ³ããããŸãããã®ã·ã°ããã£ã¯ãTypeScriptã³ã³ãã€ã©ã«å¯ŸããŠã第äºåŒæ°ãçæããã`AllPermissions`ãŠããªã³åã®æååã®ããããã§ãªããã°ãªããªãããšãäŒããŸããç°ãªãæååã䜿çšããããšãããšãã³ã³ãã€ã«æãšã©ãŒãçºçããŸãã
å®è·µã§ã®äœ¿çšäŸ
ãããæ¥åžžã®ã³ãŒãã£ã³ã°ãã©ã®ããã«å€ãããèŠãŠã¿ãŸããããNode.js/Expressã¢ããªã±ãŒã·ã§ã³ã§APIãšã³ããã€ã³ããä¿è·ããå Žåãæ³åããŠãã ããã
import { hasPermission } from './authorization';
import { Permissions } from './permissions';
import { User } from './user';
app.delete('/api/posts/:id', (req, res) => {
const currentUser: User = req.user; // èªèšŒããã«ãŠã§ã¢ãããŠãŒã¶ãŒãã¢ã¿ãããããŠãããšä»®å®
// ããã¯å®ç§ã«æ©èœããŸãïŒ Permissions.POST.DELETEã®ãªãŒãã³ã³ããªãŒããå¹ããŸã
if (hasPermission(currentUser, Permissions.POST.DELETE)) {
// æçš¿ãåé€ããããžãã¯
res.status(200).send({ message: 'æçš¿ãåé€ãããŸããã' });
} else {
res.status(403).send({ error: 'æçš¿ãåé€ããæš©éããããŸããã' });
}
});
// 次ã«ãééããç¯ããŠã¿ãŸãããïŒ
app.post('/api/users', (req, res) => {
const currentUser: User = req.user;
// 次ã®è¡ã¯IDEã§èµ€ãæ³¢ç·ã衚瀺ãããã³ã³ãã€ã«ã«å€±æããŸãïŒ
// ãšã©ãŒ: å '"user:creat"' ã®åŒæ°ãå 'AllPermissions' ã®ãã©ã¡ãŒã¿ãŒã«å²ãåœãŠãããšã¯ã§ããŸããã
// '"user:create"' ã®ããšã§ããïŒ
if (hasPermission(currentUser, "user:creat")) { // 'create' ã«ã¿ã€ã
// ãã®ã³ãŒãã«ã¯å°éããŸãã
}
});
ç§ãã¡ã¯ãã°ã®äžã€ã®ã«ããŽãªå šäœãæé€ããããšã«æåããŸãããã³ã³ãã€ã©ã¯ä»ããç§ãã¡ã®ã»ãã¥ãªãã£ã¢ãã«ã匷å¶ããç©æ¥µçãªåå è ãšãªã£ãŠããŸãã
ã·ã¹ãã ã®ã¹ã±ãŒã«ã¢ããïŒåå®å šãªèªå¯ã«ãããé«åºŠãªæŠå¿µ
ã·ã³ãã«ãªããŒã«ããŒã¹ã¢ã¯ã»ã¹å¶åŸ¡ïŒRBACïŒã·ã¹ãã ã¯åŒ·åã§ãããå®éã®ã¢ããªã±ãŒã·ã§ã³ã«ã¯ããè€éãªããŒãºãããããšããããããŸããããŒã¿èªäœã«äŸåããæš©éãã©ã®ããã«æ±ãã°ããã§ããããïŒäŸãã°ã`EDITOR`ã¯æçš¿ãæŽæ°ã§ããŸãããããã¯*èªåã®*æçš¿ã ãã§ãã
屿§ããŒã¹ã¢ã¯ã»ã¹å¶åŸ¡ïŒABACïŒãšãªãœãŒã¹ããŒã¹ã®æš©é
ããã§ã屿§ããŒã¹ã¢ã¯ã»ã¹å¶åŸ¡ïŒABACïŒã®æŠå¿µãå°å ¥ããŸããã·ã¹ãã ãæ¡åŒµããŠãããªã·ãŒãæ¡ä»¶ãæ±ããããã«ããŸãããŠãŒã¶ãŒã¯äžè¬çãªæš©éïŒäŸïŒ`post:update`ïŒãæã€ã ãã§ãªããã¢ã¯ã»ã¹ããããšããŠããç¹å®ã®ãªãœãŒã¹ã«é¢é£ããã«ãŒã«ãæºããå¿ èŠããããŸãã
ããã¯ããªã·ãŒããŒã¹ã®ã¢ãããŒãã§ã¢ãã«åã§ããŸããç¹å®ã®æš©éã«å¯Ÿå¿ããããªã·ãŒã®ããããå®çŸ©ããŸãã
// src/policies.ts
import { User } from './user';
// ãªãœãŒã¹ã®åãå®çŸ©
type Post = { id: string; authorId: string; };
// ããªã·ãŒã®ããããå®çŸ©ãããŒã¯åå®å
šãªæš©éã§ãïŒ
type PolicyMap = {
[Permissions.POST.UPDATE]?: (user: User, post: Post) => boolean;
[Permissions.POST.DELETE]?: (user: User, post: Post) => boolean;
// ãã®ä»ã®ããªã·ãŒ...
};
export const policies: PolicyMap = {
[Permissions.POST.UPDATE]: (user, post) => {
// æçš¿ãæŽæ°ããã«ã¯ããŠãŒã¶ãŒãèè
ã§ãªããã°ãªããªãã
return user.id === post.authorId;
},
[Permissions.POST.DELETE]: (user, post) => {
// æçš¿ãåé€ããã«ã¯ããŠãŒã¶ãŒãèè
ã§ãªããã°ãªããªãã
return user.id === post.authorId;
},
};
// æ°ããããã匷åãªãã§ãã¯é¢æ°ãäœæã§ããŸã
export function can(user: User | null, permission: AllPermissions, resource?: any): boolean {
if (!user) return false;
// 1. ãŸãããŠãŒã¶ãŒãããŒã«ã«åºã¥ããŠåºæ¬çãªæš©éãæã£ãŠãããã確èªããŸãã
if (!hasPermission(user, permission)) {
return false;
}
// 2. 次ã«ããã®æš©éã«ç¹å®ã®ããªã·ãŒãååšãããã確èªããŸãã
const policy = policies[permission];
if (policy) {
// 3. ããªã·ãŒãååšããå Žåããããæºããå¿
èŠããããŸãã
if (!resource) {
// ããªã·ãŒã¯ãªãœãŒã¹ãå¿
èŠãšããŸãããæäŸãããŸããã§ããã
console.warn(`ããªã·ãŒ ${permission} ã¯ãªãœãŒã¹ãæäŸãããªãã£ããããã§ãã¯ãããŸããã§ããã`);
return false;
}
return policy(user, resource);
}
// 4. ããªã·ãŒãååšããªãå ŽåãããŒã«ããŒã¹ã®æš©éãæã£ãŠããã ãã§ååã§ãã
return true;
}
ããã§ãAPIãšã³ããã€ã³ãã¯ãããã¥ã¢ã³ã¹ã«å¯ã¿ãå®å šã«ãªããŸãã
import { can } from './policies';
import { Permissions } from './permissions';
app.put('/api/posts/:id', async (req, res) => {
const currentUser = req.user;
const post = await db.posts.findById(req.params.id);
// ãã®*ç¹å®ã®*æçš¿ãæŽæ°ããèœåããã§ãã¯
if (can(currentUser, Permissions.POST.UPDATE, post)) {
// ãŠãŒã¶ãŒã¯ 'post:update' æš©éãæã¡ããã€èè
ã§ãã
// æŽæ°ããžãã¯ãç¶è¡...
} else {
res.status(403).send({ error: 'ãã®æçš¿ãæŽæ°ããæš©éããããŸããã' });
}
});
ããã³ããšã³ããšã®çµ±åïŒããã¯ãšã³ããšããã³ããšã³ãéã§ã®åã®å ±æ
ãã®ã¢ãããŒãã®æã倧ããªå©ç¹ã®äžã€ã¯ãç¹ã«ããã³ããšã³ããšããã¯ãšã³ãã®äž¡æ¹ã§TypeScriptã䜿çšããŠããå Žåã«ããããã®åãå ±æã§ããããšã§ãã`permissions.ts`ã`roles.ts`ããã®ä»ã®å ±æãã¡ã€ã«ãã¢ãã¬ãïŒNxãTurborepoãLernaãªã©ã®ããŒã«ã䜿çšïŒå ã®å ±éããã±ãŒãžã«é 眮ããããšã§ãããã³ããšã³ãã¢ããªã±ãŒã·ã§ã³ã¯èªå¯ã¢ãã«ãå®å šã«èªèããããã«ãªããŸãã
ããã«ãããUIã³ãŒãã§åŒ·åãªãã¿ãŒã³ãå¯èœã«ãªããŸããäŸãã°ããŠãŒã¶ãŒã®æš©éã«åºã¥ããŠèŠçŽ ãæ¡ä»¶ä»ãã§ã¬ã³ããªã³ã°ãããªã©ããã¹ãŠåã·ã¹ãã ã®å®å šæ§ã®ããšã§è¡ããŸãã
Reactã³ã³ããŒãã³ããèããŠã¿ãŸãããã
// Reactã³ã³ããŒãã³ãå
import { Permissions } from '@my-app/shared-types'; // å
±æããã±ãŒãžããã€ã³ããŒã
import { useAuth } from './auth-context'; // èªèšŒç¶æ
ã®ããã®ã«ã¹ã¿ã ããã¯
interface EditPostButtonProps {
post: Post;
}
const EditPostButton = ({ post }: EditPostButtonProps) => {
const { user, can } = useAuth(); // 'can'ã¯æ°ããããªã·ãŒããŒã¹ã®ããžãã¯ã䜿çšããããã¯
// ãã§ãã¯ã¯åå®å
šã§ããUIã¯æš©éãšããªã·ãŒãèªèããŠããŸãïŒ
if (!can(user, Permissions.POST.UPDATE, post)) {
return null; // ãŠãŒã¶ãŒãã¢ã¯ã·ã§ã³ãå®è¡ã§ããªãå Žåã¯ãã¿ã³ãã¬ã³ããªã³ã°ããªã
}
return ;
};
ããã¯ç»æçãªããšã§ããããã³ããšã³ãã®ã³ãŒãã¯ãUIã®å¯èŠæ§ãå¶åŸ¡ããããã«æšæž¬ããããããŒãã³ãŒããããæååã䜿çšãããããå¿ èŠããªããªããŸããããã¯ãšã³ãã®ã»ãã¥ãªãã£ã¢ãã«ãšå®å šã«åæãããããã¯ãšã³ãã§æš©éã倿Žãããå ŽåãæŽæ°ãããªããšããã³ããšã³ãã§å³åº§ã«åãšã©ãŒãçºçããUIã®äžæŽåãé²ããŸãã
ããžãã¹ã±ãŒã¹ïŒãªãããªãã®çµç¹ã¯åå®å šãªèªå¯ã«æè³ãã¹ãã
ãã®ãã¿ãŒã³ãæ¡çšããããšã¯ãåãªãæè¡çãªæ¹å以äžã®ãã®ã§ãããå ·äœçãªããžãã¹äžã®ã¡ãªãããããããæŠç¥çãªæè³ã§ãã
- ãã°ã®å€§å¹ ãªåæžïŒ èªå¯ã«é¢é£ããã»ãã¥ãªãã£è匱æ§ãå®è¡æãšã©ãŒã®ã«ããŽãªå šäœãæé€ããŸããããã¯ãããå®å®ãã補åãšãã³ã¹ãã®ãããæ¬çªç°å¢ã§ã®ã€ã³ã·ãã³ãã®æžå°ã«ã€ãªãããŸãã
- éçºé床ã®å éïŒ ãªãŒãã³ã³ããªãŒããéçè§£æãèªå·±ææžåã³ãŒãã«ãããéçºè ã¯ããéããããèªä¿¡ãæã£ãŠäœæ¥ãé²ããããšãã§ããŸããæš©éæååãæ¢ãããããµã€ã¬ã³ããªèªå¯ã®å€±æããããã°ãããããæéãåæžãããŸãã
- ãªã³ããŒãã£ã³ã°ãšã¡ã³ããã³ã¹ã®ç°¡çŽ åïŒ æš©éã·ã¹ãã ã¯ãã¯ãéšæç¥èã§ã¯ãããŸãããæ°ããéçºè ã¯ãå ±æãããåã調ã¹ãããšã§ãã»ãã¥ãªãã£ã¢ãã«ãå³åº§ã«çè§£ã§ããŸããã¡ã³ããã³ã¹ãšãªãã¡ã¯ã¿ãªã³ã°ã¯ãäœãªã¹ã¯ã§äºæž¬å¯èœãªã¿ã¹ã¯ã«ãªããŸãã
- ã»ãã¥ãªãã£äœå¶ã®åŒ·åïŒ æç¢ºã§ãæç€ºçã§ãäžå 管çãããæš©éã·ã¹ãã ã¯ãç£æ»ãè«ççãªæ€èšŒãã¯ããã«å®¹æã«ãªããŸããã誰ããŠãŒã¶ãŒãåé€ããæš©éãæã£ãŠãããïŒãã®ãããªè³ªåã«äºçްãªããšã§çããããããã«ãªããŸããããã«ãããã³ã³ãã©ã€ã¢ã³ã¹ãšã»ãã¥ãªãã£ã¬ãã¥ãŒã匷åãããŸãã
課é¡ãšèæ ®äºé
ãã®ã¢ãããŒãã¯åŒ·åã§ãããèæ ®ãã¹ãç¹ããªãããã§ã¯ãããŸããã
- åæã»ããã¢ããã®è€éãïŒ ã³ãŒãå šäœã«æååãã§ãã¯ãæ£åšãããããããäºåã®ã¢ãŒããã¯ãã£èšèšãå¿ èŠã§ãããããããã®åææè³ã¯ãããžã§ã¯ãã®ã©ã€ããµã€ã¯ã«å šäœã«ããã£ãŠå©çããããããŸãã
- å€§èŠæš¡ç°å¢ã§ã®ããã©ãŒãã³ã¹ïŒ äœåãã®æš©éãéåžžã«è€éãªãŠãŒã¶ãŒéå±€ãæã€ã·ã¹ãã ã§ã¯ããŠãŒã¶ãŒã®æš©éã»ãããèšç®ããããã»ã¹ïŒ`getUserPermissions`ïŒãããã«ããã¯ã«ãªãå¯èœæ§ããããŸãããã®ãããªã·ããªãªã§ã¯ããã£ãã·ã³ã°æŠç¥ïŒäŸïŒRedisã䜿çšããŠèšç®æžã¿ã®æš©éã»ãããä¿åããïŒã®å®è£ ãäžå¯æ¬ ã§ãã
- ããŒã«ãšâŸèªã®ãµããŒãïŒ ãã®ã¢ãããŒãã®å®å šãªå©ç¹ã¯ã匷åãªéçåä»ãã·ã¹ãã ãæã€èšèªã§å®çŸãããŸããPythonãRubyã®ãããªåçåä»ãèšèªã§ã¯ãåãã³ããéçè§£æããŒã«ã§è¿äŒŒããããšã¯å¯èœã§ãããTypeScriptãC#ãJavaãRustã®ãããªèšèªã«æããã€ãã£ãã§ãã
çµè«ïŒããå®å šã§ä¿å®æ§ã®é«ãæªæ¥ãç¯ã
ç§ãã¡ã¯ãããžãã¯ã¹ããªã³ã°ãšããå±éºãªé åãããåå®å šãªèªå¯ãšããå ç¢ãªèŠå¡ãžãšæ ãããŠããŸãããæš©éãåãªãããŒã¿ãšããŠã§ã¯ãªããã¢ããªã±ãŒã·ã§ã³ã®åã·ã¹ãã ã®äžå¿çãªéšåãšããŠæ±ãããšã§ãã³ã³ãã€ã©ãåãªãã³ãŒããã§ãã«ãŒãããçšå¿æ·±ãèŠåå¡ãžãšå€è²ãããŸãã
åå®å šãªèªå¯ã¯ãéçºã©ã€ããµã€ã¯ã«ã®å¯èœãªéãæ©ãæ®µéã§ãšã©ãŒããã£ãããããšãããçŸä»£ã®ãœãããŠã§ã¢ãšã³ãžãã¢ãªã³ã°ååãã·ããã¬ãããã®èšŒã§ããããã¯ãã³ãŒãã®å質ãéçºè ã®çç£æ§ããããŠæãéèŠãªã¢ããªã±ãŒã·ã§ã³ã®ã»ãã¥ãªãã£ãžã®æŠç¥çãªæè³ã§ããèªå·±ææžåããããªãã¡ã¯ã¿ãªã³ã°ã容æã§ã誀çšãäžå¯èœãªã·ã¹ãã ãæ§ç¯ããããšã§ãããªãã¯åã«ããè¯ãã³ãŒããæžããŠããã ãã§ãªããã¢ããªã±ãŒã·ã§ã³ãšããŒã ã«ãšã£ãŠããå®å šã§ä¿å®æ§ã®é«ãæªæ¥ãç¯ããŠããã®ã§ããæ¬¡ã«æ°ãããããžã§ã¯ããéå§ããããå€ããããžã§ã¯ãããªãã¡ã¯ã¿ãªã³ã°ããããšããããããšãã¯ãèªåããŠã¿ãŠãã ãããããªãã®èªå¯ã·ã¹ãã ã¯ãããªãã®ããã«æ©èœããŠããŸããããããšãããªãã«æµå¯ŸããŠããŸããïŒ